/*
 * ProcessingScenegraph - http://transmote.com/psg/ is a
 * derivative of Processing - http://processing.org/ 
 * Copyright (C) 2011, Eric Socolofsky
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.transmote.psg;
import com.transmote.psg.events.Event;

import com.transmote.psg.events.EventDispatcher;
import com.transmote.psg.events.KeyEvent;
import com.transmote.psg.events.MouseEvent;

import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;

import processing.core.PApplet;
import processing.core.PGraphics2D;
import processing.core.PGraphicsJava2D;

/**
 * <p>
 * A PSprite instance is a single element in the Processing Scenegraph display list.
 * Any on-screen element using the PSG framework is an instance of PSprite,
 * or of a subclass of PSprite.  Processing Scenegraph is modeled on the display list
 * at the core of Adobe's <a href="http://www.adobe.com/devnet/actionscript.html">ActionScript</a>
 * programming language; PSprite is in turn modeled on ActionScript's
 * <a href="http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/display/Sprite.html">Sprite</a>
 * class, and offers much of the same functionality.
 * </p><p>
 * PSprite instances can be translated, rotated, and scaled via public properties
 * such as {@link #x}, {@link #rotationZ}, and {@link #scaleY}.
 * PSprite methods provide for addition to ({@link #addChild(PSprite)}),
 * removal from ({@link #removeChild(PSprite)}), and manipulation of
 * (e.g. {@link #swapChildren(PSprite, PSprite)}), the display list.
 * PSprite also allows for comparing between coordinate spaces,
 * with methods such as {@link #mousePt()} and {@link #screenPt()}.
 * </p><p>
 * Every frame, each PSprite instance on the display list will automatically call
 * {@link #update(PApplet)}, then each PSprite instance on the display list will call
 * {@link #draw(PApplet)}.  To implement specific functionality, such as animation
 * or rendering, developers should subclass PSprite and implement these methods as desired.
 * </p><p>
 * PSprite instances in the display list transport events through the display list.
 * See {@link com.transmote.psg.events.Event} for more detail on the event flow.
 * To handle a mouse or key event, a PSprite subclass must implement the event handler
 * corresponding to the type of mouse event to handle.
 * For mouse events to respect the bounds rect of the PSprite,
 * the {@link #bounds} rect must be set and updated manually.
 * </p><p>
 * Note that PSprite instances will not call {@link #update(PApplet)} or {@link #draw(PApplet)},
 * nor receive mouse or key events, until they are added to the display list.
 * </p>
 * 
 * @author		Eric Socolofsky
 */
public class PSprite extends EventDispatcher {
	/**
	 * The position of the centerpoint (aka 'registration point')
	 * of this PSprite instance on the x-axis of its parent. 
	 */
	public float x = 0.0f;
	
	/**
	 * The position of the centerpoint (aka 'registration point')
	 * of this PSprite instance on the y-axis of its parent. 
	 */
	public float y = 0.0f;
	
	/**
	 * The position of the centerpoint (aka 'registration point')
	 * of this PSprite instance on the z-axis of its parent. 
	 */
	public float z = 0.0f;
	
	/**
	 * <p>
	 * The width of the graphic content of this PSprite instance.
	 * </p><p>
	 * PSprite calculates its {@link #bounds()} using
	 * <tt>width</tt>, <tt>height</tt>, <tt>boundsLeft</tt>, and <tt>boundsRight</tt>.
	 * Setting any of these properties will change the bounds rect
	 * of this PSprite instance, and setting the bounds rect
	 * via {@link #setBounds(java.awt.geom.Rectangle2D.Float)} will, in turn, update those properties.
	 * </p><p>
	 * In order for this PSprite instance to handle mouse events that require the cursor
	 * be over its graphic content, <tt>width</tt> and <tt>height</tt> must both be set manually.
	 * Additionally, if the upper-left corner of the graphic content is not at (0,0),
	 * {@link #boundsLeft} and {@link #boundsTop} must be set to indicate this;
	 * <tt>width</tt> and <tt>height</tt> should be measured from the upper-left corner
	 * of the graphic content.
	 * </p><p>
	 * Perhaps, someday, Processing Scenegraph will be smart enough to update its bounds rect
	 * automatically as its graphic content changes, but for now, it's up to the
	 * application developer to manually update the bounds rect.
	 * </p>
	 * 
	 * @see #height
	 * @see #boundsLeft
	 * @see #boundsTop
	 * @see #bounds()
	 * @see #setBounds(java.awt.geom.Rectangle2D.Float)
	 * 
 	 * @example		MouseEvents_Bounds
	 */
	public float width;
	
	/**
	 * <p>
	 * The height of the graphic content of this PSprite instance.
	 * </p><p>
	 * PSprite calculates its {@link #bounds()} using
	 * <tt>width</tt>, <tt>height</tt>, <tt>boundsLeft</tt>, and <tt>boundsRight</tt>.
	 * Setting any of these properties will change the bounds rect
	 * of this PSprite instance, and setting the bounds rect
	 * via {@link #setBounds(java.awt.geom.Rectangle2D.Float)} will, in turn, update those properties.
	 * </p><p>
	 * In order for this PSprite instance to handle mouse events that require the cursor
	 * be over its graphic content, <tt>width</tt> and <tt>height</tt> must both be set manually.
	 * Additionally, if the upper-left corner of the graphic content is not at (0,0),
	 * {@link #boundsLeft} and {@link #boundsTop} must be set to indicate this;
	 * <tt>width</tt> and <tt>height</tt> should be measured from the upper-left corner
	 * of the graphic content.
	 * </p><p>
	 * Perhaps, someday, Processing Scenegraph will be smart enough to update its bounds rect
	 * automatically as its graphic content changes, but for now, it's up to the
	 * application developer to manually update the bounds rect.
	 * </p>
	 * 
	 * @see #width
	 * @see #boundsLeft
	 * @see #boundsTop
	 * @see #bounds()
	 * @see #setBounds(java.awt.geom.Rectangle2D.Float)
	 * 
 	 * @example		MouseEvents_Bounds
	 */
	public float height;
	
	/**
	 * <p>
	 * The left edge of the graphic content of this PSprite instance.
	 * </p><p>
	 * PSprite calculates its {@link #bounds()} using
	 * <tt>width</tt>, <tt>height</tt>, <tt>boundsLeft</tt>, and <tt>boundsRight</tt>.
	 * Setting any of these properties will change the bounds rect
	 * of this PSprite instance, and setting the bounds rect
	 * via {@link #setBounds(java.awt.geom.Rectangle2D.Float)} will, in turn, update those properties.
	 * </p><p>
	 * In order for this PSprite instance to handle mouse events that require the cursor
	 * be over its graphic content, <tt>width</tt> and <tt>height</tt> must both be set manually.
	 * Additionally, if the upper-left corner of the graphic content is not at (0,0),
	 * {@link #boundsLeft} and {@link #boundsTop} must be set to indicate this;
	 * <tt>width</tt> and <tt>height</tt> should be measured from the upper-left corner
	 * of the graphic content.
	 * </p><p>
	 * Perhaps, someday, Processing Scenegraph will be smart enough to update its bounds rect
	 * automatically as its graphic content changes, but for now, it's up to the
	 * application developer to manually update the bounds rect.
	 * </p>
	 * 
	 * @see #width
	 * @see #height
	 * @see #boundsTop
	 * @see #bounds()
	 * @see #setBounds(java.awt.geom.Rectangle2D.Float)
	 * 
 	 * @example		MouseEvents_Bounds
	 */
	public float boundsLeft;
	
	/**
	 * <p>
	 * The top edge of the graphic content of this PSprite instance.
	 * </p><p>
	 * PSprite calculates its {@link #bounds()} using
	 * <tt>width</tt>, <tt>height</tt>, <tt>boundsLeft</tt>, and <tt>boundsRight</tt>.
	 * Setting any of these properties will change the bounds rect
	 * of this PSprite instance, and setting the bounds rect
	 * via {@link #setBounds(java.awt.geom.Rectangle2D.Float)} will, in turn, update those properties.
	 * </p><p>
	 * In order for this PSprite instance to handle mouse events that require the cursor
	 * be over its graphic content, <tt>width</tt> and <tt>height</tt> must both be set manually.
	 * Additionally, if the upper-left corner of the graphic content is not at (0,0),
	 * {@link #boundsLeft} and {@link #boundsTop} must be set to indicate this;
	 * <tt>width</tt> and <tt>height</tt> should be measured from the upper-left corner
	 * of the graphic content.
	 * </p><p>
	 * Perhaps, someday, Processing Scenegraph will be smart enough to update its bounds rect
	 * automatically as its graphic content changes, but for now, it's up to the
	 * application developer to manually update the bounds rect.
	 * </p>
	 * 
	 * @see #width
	 * @see #height
	 * @see #boundsLeft
	 * @see #bounds()
	 * @see #setBounds(java.awt.geom.Rectangle2D.Float)
	 * 
 	 * @example		MouseEvents_Bounds
	 */
	public float boundsTop;
	
	/**
	 * The scale of this PSprite instance along the x-axis. 
	 */
	public float scaleX = 1.0f;
	
	/**
	 * The scale of this PSprite instance along the y-axis. 
	 */
	public float scaleY = 1.0f;
	
	/**
	 * The scale of this PSprite instance along the z-axis. 
	 */
	public float scaleZ = 1.0f;
	
	/**
	 * The rotation of this PSprite instance along the x-axis, in radians. 
	 */
	public float rotationX = 0.0f;
	
	/**
	 * The rotation of this PSprite instance along the y-axis, in radians. 
	 */
	public float rotationY = 0.0f;
	
	/**
	 * The rotation of this PSprite instance along the z-axis, in radians. 
	 */
	public float rotationZ = 0.0f;
	
	/**
	 * The on-screen visibility of this PSprite instance. 
	 */
	public boolean visible = true;
	
	/**
	 * <p>
	 * Specifies whether this PSprite instance receives input events,
	 * such as MouseEvents and KeyEvents.  The default value is <tt>true</tt>,
	 * which means that by default any PSprite on the display list receives input events.
	 * </p><p>
	 * Any children of this instance on the display list are not affected.
	 * To prevent or enable input events reaching children of this instance,
	 * use {@link #inputChildren}.
	 * </p>
	 */
	public boolean inputEnabled = true;
	
	/**
	 * Specifies whether children of this PSprite instance receive input events,
	 * such as MouseEvents and KeyEvents.  The default value is <tt>true</tt>,
	 * which means that by default all children of this instance receive input events. 
	 */
	public boolean inputChildren = true;
	
	/**
	 * <p>
	 * Specifies whether input events (e.g. MouseEvent, KeyEvent) in the CAPTURE phase
	 * will be handled by input event handlers.  Setting to <tt>true</tt> does not affect
	 * handling of input events in the AT_TARGET or CAPTURE phases.
	 * </p>
	 * <p>
	 * Note that due to the implementation used in Processing Scenegraph,
	 * which entails running a hitTest() on all descendants of this PSprite instance,
	 * setting to <tt>true</tt> may result in decreased performance.
	 * The implemenation can probably use some optimization.
	 * </p>
	 * <p> 
	 * Defaults to <tt>false</tt>.
	 * </p>
	 */
	public boolean handleCaptureEvents = false;
	
	PSprite parent;
	ArrayList<PSprite> childList;
	PApplet p;
	float screenX;
	float screenY;
	float mouseX;
	float mouseY;
	
	private AffineTransform transform;
	private Point2D.Float localPt;
	private Point2D.Float screenPt;
	private Point2D.Float mousePt;
	private Rectangle2D.Float bounds = new Rectangle2D.Float();
	
	private boolean bRendererIs3D = false;
	private boolean bDisposed = false;
	
	//-----<CONSTRUCTORS>--------------------------------------------//
	/**
	 * Creates a new PSprite instance with a zero bounds rect.
	 */
	public PSprite () {
		this(new Rectangle2D.Float());
	}
	
	/**
	 * Creates a new PSprite instance with a bounds rect positioned at (0,0),
	 * of a specified width and height.
	 * 
	 * @param	width		Width of the bounds rect.
	 * @param	height		Height of the bounds rect.
	 */
	public PSprite (float width, float height) {
		this(new Rectangle2D.Float(0, 0, width, height));
	}
	
	/**
	 * Creates a new PSprite instance with a bounds rect with its upper-left corner
	 * at a specified location, with a specified width and height.
	 * 
	 * @param	boundsLeft	Left edge of the bounds rect.
	 * @param	boundsTop	Top edge of the bounds rect.
	 * @param	width		Width of the bounds rect.
	 * @param	height		Height of the bounds rect.
	 */
	public PSprite (float boundsLeft, float boundsTop, float width, float height) {
		this(new Rectangle2D.Float(boundsLeft, boundsTop, width, height));
	}
	
	/**
	 * Creates a new PSprite instance with a specified bounds rect.
	 * 
	 * @param	bounds		The specified bounds rect.
	 */
	public PSprite (Rectangle2D.Float bounds) {
		setBounds(bounds);
		init();
	}
	//-----</CONSTRUCTORS>-------------------------------------------//
	
	
	
	//-----<DISPLAY LIST>--------------------------------------------//
	/**
	 * Adds a PSprite as a child to this PSprite instance.
	 * The child is added to the front (top) of all other children of this PSprite instance.
	 * @param	sprite	The PSprite instance to add as a child.
	 * @example			AddChild
	 */
	public void addChild (PSprite sprite) {
		addChild(Integer.MAX_VALUE, sprite);
	}
	
	/**
	 * Adds a PSprite as a child to this PSprite instance.
	 * The child is added at the index specified.
	 * @param	index	The index in the child list at which the child is added.
	 * @param	sprite	The PSprite instance to add as a child.
	 * @example			AddChild
	 */
	public void addChild (int index, PSprite sprite) {
		if (sprite == this) {
			// PSprite cannot be a child of itself.
			return;
		}
		
		if (sprite.parent != null && sprite.parent != this) {
			// if sprite already belongs to another child list, remove it from that parent
			sprite.parent.removeChild(sprite);
		}
		
		if (!childList.contains(sprite)) {
			childList.add(Math.min(index, childList.size()), sprite);
		}
		
		sprite.onAdded(this);
	}
	
	/**
	 * Removes the specified PSprite from the child list of this PSprite instance.
	 * @param	sprite	The PSprite instance to remove.
	 * @return			The removed PSprite instance.
	 */
	public PSprite removeChild (PSprite sprite) {
		childList.remove(sprite);
		sprite.onRemoved();
		return sprite;
	}
	
	/**
	 * Removes the PSprite at the specified index from the child list of this PSprite instance.
	 * @param	index	The index in the child list of this PSprite instance of the child to remove.
	 * @return			The removed PSprite instance.
	 */
	public PSprite removeChild (int index) {
		if (index >= childList.size()) {
			throw new IndexOutOfBoundsException("Index "+ index +" is out of range "+ childList.size() +" for PSprite child list.");
		}
		
		return childList.remove(index);
	}
	
	/**
	 * Returns the PSprite at the specified index in the child list of this PSprite instance.
	 * @param	index	The index in the child list of this PSprite instance of the child to return.
	 */
	public PSprite getChildAt (int index) {
		if (index >= childList.size()) {
			throw new IndexOutOfBoundsException("Index "+ index +" is out of range "+ childList.size() +" for PSprite child list.");
		}
		
		return childList.get(index);
	}
	
	/**
	 * Return the index of the specified PSprite in the child list of this PSprite instance. 
	 * @param		sprite	The PSprite instance whose index will be returned.
	 * @return		index	The index of the specified PSprite instance.
	 */
	public int getChildIndex (PSprite sprite) {
		if (!childList.contains(sprite)) {
			return -1;
		}
		return childList.indexOf(sprite);
	}
	
	/**
	 * Move a child of this PSprite instance to a specified index in the child list.
	 * @param	index	The index to which to move the specified PSprite instance.
	 * @param 	sprite	The PSprite child to move.
	 * @throws	IndexOutOfBoundsException	Thrown if the specified index position does not exist in the child list.
	 * @throws	IllegalArgumentException	Thrown if the specified PSprite is not a child of this PSprite instance.
	 */
	public void setChildIndex (int index, PSprite sprite) throws IndexOutOfBoundsException, IllegalArgumentException {
		if (!childList.contains(sprite)) {
			throw new IllegalArgumentException("Passed PSprite must be a child of this PSprite.");
		}
		if (index >= childList.size()) {
			throw new IndexOutOfBoundsException("Index "+ index +" is out of range "+ childList.size() +" for PSprite child list.");
		}
		
		childList.remove(sprite);
		childList.add(index, sprite);
	}
	
	/**
	 * Swap the index positions of two child PSprites, given the two children.
	 * @param	sprite1		One child to swap.
	 * @param	sprite2		The other child to swap.
	 * @throws	IllegalArgumentException	Thrown if either specified PSprite is not a child of this PSprite instance.
	 */
	public void swapChildren (PSprite sprite1, PSprite sprite2) throws IllegalArgumentException {
		if (!childList.contains(sprite1)) {
			throw new IllegalArgumentException("@sprite1 must be a child of this PSprite.");
		}
		if (!childList.contains(sprite2)) {
			throw new IllegalArgumentException("@sprite2 must be a child of this PSprite.");
		}
		
		Collections.swap(childList, childList.indexOf(sprite1), childList.indexOf(sprite2));
	}
	
	/**
	 * Swap the index positions of two child PSprites, given the indices of the two children in the child list.
	 * @param	index1		The index of one child to swap.
	 * @param	index2		The index of the other child to swap.
	 * @throws	IllegalArgumentException	Thrown if either specified index position does not exist in the child list.
	 */
	public void swapChildren (int index1, int index2) throws IndexOutOfBoundsException {
		if (index1 >= childList.size() || index1 < 0) {
			throw new IndexOutOfBoundsException("Index "+ index1 +" is out of range "+ childList.size() +" for PSprite child list.");
		}
		if (index2 >= childList.size() || index2 < 0) {
			throw new IndexOutOfBoundsException("Index "+ index2 +" is out of range "+ childList.size() +" for PSprite child list.");
		}
		
		Collections.swap(childList, index1, index2);
	}
	
	/**
	 * Returns the number of children in the child list of this PSprite instance.
	 */
	public int numChildren () {
		return childList.size();
	}
	//-----</DISPLAY LIST>-------------------------------------------//
	
	
	
	//-----<ACCESSORS>-----------------------------------------------//
	/**
	 * <p>
	 * The bounds rect of the graphic content of this PSprite instance,
	 * in the coordinate system of this PSprite.
	 * </p><p>
	 * PSprite calculates its {@link #bounds()} using
	 * <tt>width</tt>, <tt>height</tt>, <tt>boundsLeft</tt>, and <tt>boundsRight</tt>.
	 * Setting any of these properties will change the bounds rect
	 * of this PSprite instance, and setting the bounds rect
	 * via {@link #setBounds(java.awt.geom.Rectangle2D.Float)} will, in turn, update those properties.
	 * </p><p>
	 * In order for this PSprite instance to handle mouse events that require the cursor
	 * be over its graphic content, <tt>width</tt> and <tt>height</tt> must both be set manually.
	 * Additionally, if the upper-left corner of the graphic content is not at (0,0),
	 * {@link #boundsLeft} and {@link #boundsTop} must be set to indicate this;
	 * <tt>width</tt> and <tt>height</tt> should be measured from the upper-left corner
	 * of the graphic content.
	 * </p><p>
	 * Perhaps, someday, Processing Scenegraph will be smart enough to update its bounds rect
	 * automatically as its graphic content changes, but for now, it's up to the
	 * application developer to manually update the bounds rect.
	 * </p>
	 * 
	 * @see #width
	 * @see #height
	 * @see #boundsLeft
	 * @see #boundsTop
	 * @see #bounds()
	 * @see #setBounds(java.awt.geom.Rectangle2D.Float)
	 */
	public Rectangle2D.Float bounds () {
		bounds.x = boundsLeft;
		bounds.y = boundsTop;
		bounds.width = width;
		bounds.height = height;
		return bounds;
	}
	
	/**
	 * <p>
	 * Manually update the bounds rect of the graphic content of this PSprite instance.
	 * </p><p>
	 * In order for this PSprite instance to handle mouse events that require the cursor
	 * be over its graphic content, its bounds rect must be set manually.
	 * This can be done by setting <tt>width</tt>, <tt>height</tt>, <tt>boundsLeft</tt>, and <tt>boundsTop</tt>,
	 * or using the <tt>setBounds()</tt> method.
	 * </p><p>
	 * Perhaps, someday, Processing Scenegraph will be smart enough to update its bounds rect
	 * automatically as its graphic content changes, but for now, it's up to the
	 * application developer to manually update the bounds rect.
	 * </p>
	 * 
	 * @see #width
	 * @see #height
	 * @see #boundsLeft
	 * @see #boundsTop
	 * @see #bounds()
	 * @see #setBounds(java.awt.geom.Rectangle2D.Float)
	 * 
 	 * @example		MouseEvents_Bounds
	 */
	public void setBounds (float x, float y, float width, float height) {
		this.boundsLeft = x;
		this.boundsTop = y;
		this.width = width;
		this.height = height;
	}
	
	/**
	 * <p>
	 * Manually update the bounds rect of the graphic content of this PSprite instance.
	 * </p><p>
	 * In order for this PSprite instance to handle mouse events that require the cursor
	 * be over its graphic content, its bounds rect must be set manually.
	 * This can be done by setting <tt>width</tt>, <tt>height</tt>, <tt>boundsLeft</tt>, and <tt>boundsTop</tt>,
	 * or using the <tt>setBounds()</tt> method.
	 * </p><p>
	 * Perhaps, someday, Processing Scenegraph will be smart enough to update its bounds rect
	 * automatically as its graphic content changes, but for now, it's up to the
	 * application developer to manually update the bounds rect.
	 * </p>
	 * 
	 * @see #width
	 * @see #height
	 * @see #boundsLeft
	 * @see #boundsTop
	 * @see #bounds()
	 * @see #setBounds(float, float, float, float)
	 * 
 	 * @example		MouseEvents_Bounds
	 */
	public void setBounds (Rectangle2D.Float bounds) {
		this.boundsLeft = bounds.x;
		this.boundsTop = bounds.y;
		this.width = bounds.width;
		this.height = bounds.height;
	}
	
	/**
	 * Returns the PApplet instance used in this application.
	 */
	public PApplet pApplet () {
		return p;
	}
	
	/**
	 * The PSprite that contains this PSprite instance.
	 * The parent is the PSprite immediately above (behind) this PSprite instance in the display list.
	 */
	public PSprite parent () {
		return parent;
	}
	
	/**
	 * Returns true if <tt>dispose()</tt> has been called,
	 * and this PSprite instance is prepared for garbage collection.
	 */
	public boolean isDisposed () {
		return bDisposed;
	}
	
	/**
	 * Returns the x coordinate of the mouse relative to the coordinate system of this PSprite instance.
	 */
	public float mouseX () {
		return mouseX;
	}
	
	/**
	 * Returns the y coordinate of the mouse relative to the coordinate system of this PSprite instance.
	 */
	public float mouseY () {
		return mouseY;
	}
	
	/**
	 * Returns the x and y coordinates of the mouse relative to the coordinate system of this PSprite instance.
	 */
	public Point2D.Float mousePt () {
		return new Point2D.Float(mouseX, mouseY);
	}
	
	/**
	 * Returns the x coordinate of this PSprite instance relative to the coordinate system of the application.
	 */
	public float screenX () {
		return screenX;
	}
	
	/**
	 * Returns the y coordinate of this PSprite instance relative to the coordinate system of the application.
	 */
	public float screenY () {
		return screenY;
	}
	
	/**
	 * Returns the x and y coordinates of this PSprite instance relative to the coordinate system of the application.
	 */
	public Point2D.Float screenPt () {
		return new Point2D.Float(screenX, screenY);
	}
	//-----</ACCESSORS>----------------------------------------------//
	
	
	
	//-----<MISC PUBLIC METHODS>-------------------------------------//
	/**
	 * Transform a point from the local coordinate space of this PSprite instance
	 * to the coordinate space of the top-level PSpriteContainer.
	 * 
	 * @param	localPt		A point, in the local coordinate space, to transform.
	 */
	public Point2D.Float localToGlobal (Point2D localPt) {
//		TODO: can i just use this.transform as it currently exists?
//			or do i need to step through parents to get accurate transform?
//		TODO: should i be manipulating transform here?
//			or make a new instance for these calculations,
//			to keep transform accurate for other things?
		Point2D.Float globalPt = new Point2D.Float();
		
		PSprite p = this;
		transform.setToIdentity();
		while (p != null) {
			transform.translate(p.x, p.y);
			transform.scale(p.scaleX, p.scaleY);
			transform.rotate(p.rotationZ);
			p = p.parent;
		}
		transform.transform(localPt, globalPt);
		
		return globalPt;
	}
	
	/**
	 * Transform a point from the coordinate space of the top-level PSpriteContainer
	 * to the local coordinate space of this PSprite instance.
	 * 
	 * @param	localPt		A point, in the coordinate space of the
	 * 						top-level PSpriteContainer, to transform.
	 */
	public Point2D.Float globalToLocal (Point2D globalPt) {
//		TODO: can i just use this.transform as it currently exists?
//			or do i need to step through parents to get accurate transform?
//		TODO: should i be manipulating transform here?
//			or make a new instance for these calculations,
//			to keep transform accurate for other things?
		Point2D.Float localPt = new Point2D.Float();
		
		PSprite p = this;
		transform.setToIdentity();
		while (p != null) {
			transform.rotate(-p.rotationZ);
			transform.scale(1/p.scaleX, 1/p.scaleY);
			transform.translate(-p.x, -p.y);
			p = p.parent;
		}
		transform.transform(globalPt, localPt);
		
		return localPt;
	}
	
	/**
	 * Check if a point is within the bounds rect of this PSprite instance
	 * or the bounds rect of any of its children.
	 * 
	 * @param	pt	Point, in the coordinate system of this PSprite,
	 * 				to check against bounds of this PSprite instance
	 * 				and all descendants of this PSprite instance.
	 * @return	Returns <tt>true</tt> if the Point is within the bounds rect
	 * 			of this PSprite instance or any of its descendants.
	 */
	public boolean hitTest (Point2D pt) {
		return hitTest(pt, true);
	}
	
	/**
	 * Check if a point is within the bounds rect of this PSprite instance
	 * or the bounds rect of any of its children.
	 * 
	 * @param	pt				Point, in the coordinate system of this PSprite,
	 * 							to check against bounds of this PSprite instance.
	 * @param	testChildren	If true, the hit test will be performed against
	 * 							all descendants of this PSprite instance.
	 * @return	Returns <tt>true</tt> if the Point is within the bounds rect
	 * 			of this PSprite instance or, if <tt>testChildren</tt> is <tt>true</tt>,
	 * 			the bounds rect of any of its descendants.
	 */
	public boolean hitTest (Point2D pt, boolean testChildren) {
		if (bounds().contains(pt)) {
			return true;
		}
		
		PSprite child;
		Point2D.Float childPt = (Point2D.Float)(pt.clone());
		transform.setToIdentity();
		for (int i=0; i<childList.size(); i++) {
			child = childList.get(i);
			transform.rotate(-child.rotationZ);
			transform.scale(1/child.scaleX, 1/child.scaleY);
			transform.translate(-child.x, -child.y);
			transform.transform(pt, childPt);
			
			if (child.hitTest(childPt)) {
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Frees up this PSprite for garbage collection.
	 * NOTE: not yet tested, nor verified in a profiler.
	 */
	public void dispose () {
		dispose(false);
	}
	
	/**
	 * Frees up this PSprite for garbage collection.
	 * NOTE: not yet tested, nor verified in a profiler.
	 * 
	 * @param	disposeChildren		Pass <tt>true</tt> to also dispose of all children. 
	 */
	public void dispose (boolean disposeChildren) {
		if (disposeChildren) {
			if (childList != null) {
				int i = childList.size();
				while (i-- > 0) {
					childList.get(i).dispose(true);
				}
			}
		}
		
		if (childList != null) {
			childList.clear();
			childList = null;
		}
		
		parent = null;
		p = null;
		
		bDisposed = true;
		
		super.dispose();
	}
	//-----</MISC PUBLIC METHODS>------------------------------------//
	
	
	
	//-----<MOUSE EVENTS>--------------------------------------------//	
	/**
	 * Dispatched when the mouse is pressed and released within the bounds rect of this PSprite instance,
	 * with no mouse movement in between.
	 * @param e		A MouseEvent instance that describes the event.
	 */
	protected void mouseClicked (MouseEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when the mouse is pressed and released outside the bounds rect of this PSprite instance,
	 * with no mouse movement in between.
	 * @param e		A MouseEvent instance that describes the event.
	 */
	protected void mouseClickedOutside (MouseEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when the mouse travels into the bounds rect of this PSprite instance.
	 * NOTE: Not yet implemented (correctly).
	 * @param e		A MouseEvent instance that describes the event.
	 */
	protected void mouseEntered (MouseEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when the mouse travels out of the bounds rect of this PSprite instance.
	 * NOTE: Not yet implemented (correctly).
	 * @param e		A MouseEvent instance that describes the event.
	 */
	protected void mouseExited (MouseEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when the mouse is pressed within the bounds rect of this PSprite instance.
	 * @param e		A MouseEvent instance that describes the event.
	 */
	protected void mousePressed (MouseEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when the mouse is pressed outside of the bounds rect of this PSprite instance.
	 * @param e		A MouseEvent instance that describes the event.
	 */
	protected void mousePressedOutside (MouseEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when the mouse is released within the bounds rect of this PSprite instance.
	 * @param e		A MouseEvent instance that describes the event.
	 */
	protected void mouseReleased (MouseEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when the mouse is released outside of the bounds rect of this PSprite instance.
	 * @param e		A MouseEvent instance that describes the event.
	 */
	protected void mouseReleasedOutside (MouseEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when the mouse is pressed,
	 * whether within the bounds rect of this PSprite instance or not.
	 * @param e		A MouseEvent instance that describes the event.
	 */
	protected void mouseDown (MouseEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when the mouse is released,
	 * whether within the bounds rect of this PSprite instance or not.
	 * @param e		A MouseEvent instance that describes the event.
	 */
	protected void mouseUp (MouseEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when the mouse button is pressed and the mouse
	 * is moved within the bounds rect of this PSprite instance or not.
	 * @param e		A MouseEvent instance that describes the event.
	 */
	protected void mouseDragged (MouseEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when the mouse is moved, whether within the bounds rect of this PSprite instance or not.
	 * @param e		A MouseEvent instance that describes the event.
	 */
	protected void mouseMoved (MouseEvent e) {
		// optionally implemented by subclasses
	}
	//-----</MOUSE EVENTS>-------------------------------------------//
	
	
	
	//-----<KEY EVENTS>----------------------------------------------//	
	/**
	 * Dispatched when a key is pressed.
	 * @param e		A KeyEvent instance that describes the event.
	 */
	protected void keyPressed (KeyEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when a key is released.
	 * @param e		A KeyEvent instance that describes the event.
	 */
	protected void keyReleased (KeyEvent e) {
		// optionally implemented by subclasses
	}
	
	/**
	 * Dispatched when a key is pressed and released.
	 * @param e		A KeyEvent instance that describes the event.
	 */
	protected void keyTyped (KeyEvent e) {
		// optionally implemented by subclasses
	}
	//-----</KEY EVENTS>---------------------------------------------//	
	
	
	
	//-----<MISC PROTECTED METHODS>----------------------------------//	
	/**
	 * Shortcut for <tt>System.out.println(Object)</tt>.
	 * @param o
	 */
	protected void println (Object o) {
		PApplet.println(o);
	}
	
	/**
	 * Perform any pre-rendering updates,
	 * such as frame-based animation.
	 * 
	 * @param	p	PApplet instance, used for making Processing drawing calls.
	 * @example		CustomSprites
	 */
	protected void update (PApplet p) {
		// optionally implemented by subclasses
	}
	
	/**
	 * All drawing to screen should go in this method of subclasses.
	 * draw() is called after applying all translation and rotation.
	 * 
	 * @param	p	PApplet instance, used for making Processing drawing calls.
	 * @example		CustomSprites
	 */
	protected void draw (PApplet p) {
		// optionally implemented by subclasses
	}
	//-----</MISC PROTECTED METHODS>---------------------------------//
	
	
	
	//-----<PACKAGE-PRIVATE DISPLAY LIST>----------------------------//
	void onAdded (PSprite _parent) {
		parent = _parent;
		
		// traverse up the display list up to PSpriteContainer.
		// if this PSprite is not on-screen (if a PSpriteContainer is not an ancestor),
		// do not call onAddedToStage until it is.
		if (parent == null) { return; }
		
		try {
			updateTransforms();
		} catch (NoninvertibleTransformException e) {
			System.out.println("PSprite.onAdded Exception:");
			e.printStackTrace();
		}
		
		p = parent.p;
		this.dispatchEvent(new Event(Event.ADDED));
		
		PSprite parentSprite = parent;
		while (parentSprite.parent != null) {
			parentSprite = parentSprite.parent;
		}
		if (!(parentSprite instanceof PSpriteContainer)) {
			return;
		}
		
		// traverse down the display list,
		// ensuring that all descendants have called onAddedToStage.
		// TODO: is there a fail-fast way to iterate through childList so i don't have to rewrite this iteration logic multiple times?
		PSprite child;
		for (int i=0; i<childList.size(); i++) {
			child = childList.get(i);
			child.onAdded(this);
			if (i < childList.size() && childList.get(i) != child) {
				// child moved to different index or removed from childList entirely;
				// update iterator index to ensure no elements are skipped.
				i--;
			}
		}
		
		onAddedToStage();
	}
	
	void onAddedToStage () {
		bRendererIs3D = !(p.g instanceof PGraphics2D || p.g instanceof PGraphicsJava2D);
		
		this.dispatchEvent(new Event(Event.ADDED_TO_STAGE));
	}
	
	void onRemoved () {
		parent = null;
		p = null;
		this.dispatchEvent(new Event(Event.REMOVED));
	}
	//-----</PACKAGE-PRIVATE DISPLAY LIST>---------------------------//
	
	
	
	//-----<PACKAGE-PRIVATE FRAME UPDATES>---------------------------//
	/**
	 * update() self, then prerender() all children.
	 * @throws NoninvertibleTransformException 
	 */
	void prerender () throws NoninvertibleTransformException {
		update(p);
		
		// dispose() called from within update(), so bail
		if (bDisposed) { return; }
		
		updateTransforms();
		
		// TODO: is there a fail-fast way to iterate through childList so i don't have to rewrite this iteration logic multiple times?
		PSprite child;
		for (int i=0; i<childList.size(); i++) {
			child = childList.get(i);
			child.prerender();
			if (i < childList.size() && childList.get(i) != child) {
				// child moved to different index or removed from childList entirely;
				// update iterator index to ensure no elements are skipped.
				i--;
			}
		}
	}
	
	/**
	 * Translate and rotate,
	 * draw() self, then repeat process recursively through all children.
	 * 
	 * @param	p	PApplet instance, used for making Processing drawing calls.
	 */
	void render (PApplet p) {
		if (!visible) { return; }
		
		p.pushMatrix();
			
			if (bRendererIs3D) {
				p.translate(x, y, z);
				
				p.scale(scaleX, scaleY, scaleZ);
				
				// TODO: better to calculate rotation around x/y/z vector?
				p.rotateX(rotationX);
				p.rotateY(rotationY);
				p.rotateZ(rotationZ);
			} else {
				p.translate(x, y);
				
				p.scale(scaleX, scaleY);
				
				p.rotate(rotationZ);
			}
			
			// draw self below children
			draw(p);
			
			// dispose() called from within draw(), so bail
			if (bDisposed) { return; }
			
			// draw children above self
			renderChildren();
			
		p.popMatrix();
	}
	//-----</PACKAGE-PRIVATE FRAME UPDATES>--------------------------//
	
	
	
	/**
	 * Send events through the display list,
	 * starting at PSpriteContainer and progressing
	 * down through the display list (capture phase),
	 * to the front-most PSprite.
	 * 
	 * @return	Returns true if event is a bounded MouseEvent and
	 * 			occurred within the bounds of this PSprite instance.
	 */
	boolean processDisplayListEvent (Event evt) {
		if (handleCaptureEvents) {
			// while traversing down through the display list,
			// the event is in the CAPTURE phase.
			handleDisplayListEvent(evt, hitTest(mousePt));
		}
		
		if (evt.isPropagationStopped()) {
			return false;
		}
		
		boolean inChildBounds = false;
		
		if (inputChildren) {
			// propagate down through children (continue CAPTURE phase)
			// TODO: is there a fail-fast way to iterate through childList so i don't have to rewrite this iteration logic multiple times?
			PSprite child;
			for (int i=0; i<childList.size(); i++) {
				super.setEventPhase(evt, Event.PHASE.CAPTURE);
				
				child = childList.get(i);
				if (child.processDisplayListEvent(evt)) {
					inChildBounds = true;
				}
				
				if (evt.isPropagationStopped()) {
					return false;
				}
				
				if (i < childList.size() && childList.get(i) != child) {
					// child moved to different index or removed from childList entirely;
					// update iterator index to ensure no elements are skipped.
					i--;
				}
			}
		}
		
		// at this point, all descendants have processed the event.
		// by default, the event is now at target phase;
		// but if a child has already handled the event,
		// it is now bubbling back up.
		super.setEventPhase(evt, Event.PHASE.AT_TARGET);
		
		if (inputEnabled) {
			if (inChildBounds) {
				// already handled by child; handle as bubbling event
				super.setEventPhase(evt, Event.PHASE.BUBBLING);
			}
			return handleDisplayListEvent(evt, inChildBounds);
		} else {
			if (inChildBounds) {
				// allow child events to bubble up past this instance
				return bounds().contains(mousePt);
			} else {
				// if not a bubbling child event, and inputEnabled == false,
				// do not allow event to bubble up (treat as if never captured).
				return false;
			}
		}
	}
	
	
	
	/**
	 * <p>
	 * Implementation of PApplet MouseEvent and KeyEvent registration.
	 * Handles MouseEvents and KeyEvents sent from PSpriteContainer
	 * down through the display list, and back up again.
	 * </p><p>
	 * To handle MouseEvents, PSprite subclasses can override the MouseEvent handler methods:
	 * <ul>
	 * 	<li><tt>mouseClicked</tt></li>
	 * 	<li><tt>mouseEntered</tt></li>
	 * 	<li><tt>mouseExited</tt></li>
	 * 	<li><tt>mousePressed</tt></li>
	 * 	<li><tt>mouseReleased</tt></li>
	 * 	<li><tt>mouseDown</tt></li>
	 * 	<li><tt>mouseUp</tt></li>
	 * 	<li><tt>mouseDragged</tt></li>
	 * 	<li><tt>mouseMoved</tt></li>
	 * </ul>
	 * </p><p>
	 * To handle KeyEvents, PSprite subclasses can override the KeyEvent handler methods:
	 * <ul>
	 * 	<li><tt>keyPressed</tt></li>
	 * 	<li><tt>keyReleased</tt></li>
	 * 	<li><tt>keyTyped</tt></li>
	 * </ul>
	 * </p>
	 */
	protected final boolean handleDisplayListEvent (Event evt, boolean inChildBounds) {
		super.handleDisplayListEvent(evt, inChildBounds);
		
		boolean inBounds = inChildBounds ? true : bounds().contains(mousePt);
		
		switch (evt.type()) {
			case MouseEvent.MOUSE_CLICKED :
				if (inBounds) {
					mouseClicked((MouseEvent)evt);
				} else {
					mouseClickedOutside((MouseEvent)evt);
				}
				break;
			case MouseEvent.MOUSE_ENTERED :
				if (inBounds) {
					mouseEntered((MouseEvent)evt);
				}
				break;
			case MouseEvent.MOUSE_EXITED :
				if (!inBounds) {
					mouseExited((MouseEvent)evt);
				}
				break;
			case MouseEvent.MOUSE_PRESSED :
				mouseDown((MouseEvent)evt);
				if (inBounds) {
					mousePressed((MouseEvent)evt);
				} else {
					mousePressedOutside((MouseEvent)evt);
				}
				break;
			case MouseEvent.MOUSE_RELEASED :
				mouseUp((MouseEvent)evt);
				if (inBounds) {
					mouseReleased((MouseEvent)evt);
				} else {
					mouseReleasedOutside((MouseEvent)evt);
				}
				break;
			case MouseEvent.MOUSE_DRAGGED :
				if (inBounds) {
					mouseDragged((MouseEvent)evt);
				}
				break;
			case MouseEvent.MOUSE_MOVED :
				mouseMoved((MouseEvent)evt);
				break;
			case KeyEvent.KEY_PRESSED :
				keyPressed((KeyEvent)evt);
				break;
			case KeyEvent.KEY_RELEASED :
				keyReleased((KeyEvent)evt);
				break;
			case KeyEvent.KEY_TYPED :
				keyTyped((KeyEvent)evt);
				break;
		}
		
		return inBounds;
	}
	
	private void init () {
		childList = new ArrayList<PSprite>();
		
		transform = new AffineTransform();
		localPt = new Point2D.Float();
		screenPt = new Point2D.Float();
		mousePt = new Point2D.Float();
	}
	
	private void updateTransforms () throws NoninvertibleTransformException {
		if (parent == null) { return; }
		
		// TODO: account for rotation in all three axes; start with just rotZ.
		
		localPt.x = x;
		localPt.y = y;
		screenPt.x = screenPt.y = 0;
		mousePt.x = parent.mouseX;
		mousePt.y = parent.mouseY;
		
		transform.setToIdentity();
		transform.translate(parent.x, parent.y);
		transform.scale(parent.scaleX, parent.scaleY);
		transform.rotate(parent.rotationZ);
		transform.transform(localPt, screenPt);
		
		transform.setToIdentity();
		transform.translate(x, y);
		transform.scale(scaleX, scaleY);
		transform.rotate(rotationZ);
		transform.inverseTransform(new Point2D.Float(parent.mouseX, parent.mouseY), mousePt);
		
		screenX = screenPt.x;
		screenY = screenPt.y;
		mouseX = mousePt.x;
		mouseY = mousePt.y;
	}
	
	private void renderChildren () {
		// TODO: is there a fail-fast way to iterate through childList so i don't have to rewrite this iteration logic multiple times?
		PSprite child;
		for (int i=0; i<childList.size(); i++) {
			child = childList.get(i);
			child.render(p);
			if (i < childList.size() && childList.get(i) != child) {
				// child moved to different index or removed from childList entirely;
				// update iterator index to ensure no elements are skipped.
				i--;
			}
		}
	}
}